<!-- ---------------------------------------------------------------------
 // uQRCode 二维码生成插件 v3.2.2
 // 
 // uQRCode 是一款使用方式简单，高扩展的二维码生成插件。支持全端生成，支持canvas的地方就可以使用uQRCode。
 // 
 // Copyright (c) Sansnn uQRCode All rights reserved.
 // Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
 // 复制使用请保留本段注释，感谢支持开源！
 // 
 // 开源地址：
 // https://github.com/Sansnn/uQRCode
 // 
 // uni-app插件市场地址：
 // https://ext.dcloud.net.cn/plugin?id=1287
--------------------------------------------------------------------- -->
<template>
	<view class="u-qrcode" :style="{ width: `${size}px`, height: `${size}px` }">
		<!-- 画布 -->
		<!-- #ifndef MP-WEIXIN || APP-NVUE -->
		<canvas class="u-qrcode-canvas" :id="canvasId" :canvas-id="canvasId" :style="{
        width: `${templateOptions.canvasWidth}px`,
        height: `${templateOptions.canvasHeight}px`,
        transform: `scale(${size / templateOptions.canvasWidth}, ${size / templateOptions.canvasHeight})`
      }" @click="onClick" v-if="templateOptions.canvasDisplay"></canvas>
		<!-- #endif -->

		<!-- 微信小程序非2d模式不支持transform所以使用canvas2d -->
		<!-- #ifdef MP-WEIXIN -->
		<canvas class="u-qrcode-canvas" type="2d" :id="canvasId" :canvas-id="canvasId" :style="{
        width: `${templateOptions.canvasWidth}px`,
        height: `${templateOptions.canvasHeight}px`
      }" @click="onClick" v-if="templateOptions.canvasDisplay"></canvas>
		<!-- #endif -->

		<!-- nvue用gcanvas -->
		<!-- #ifdef APP-NVUE -->
		<gcanvas class="u-qrcode-canvas" ref="gcanvas" :style="{
        width: `${templateOptions.canvasWidth}px`,
        height: `${templateOptions.canvasWidth}px`
      }" @click="onClick" v-if="templateOptions.canvasDisplay"></gcanvas>
		<!-- #endif -->

		<!-- H5保存提示 -->
		<!-- #ifdef H5 -->
		<view class="uqrcode-h5-save" v-if="isH5Save">
			<image class="uqrcode-h5-save-image" :src="tempFilePath"></image>
			<text class="uqrcode-h5-save-text">若保存失败，请长按二维码进行保存</text>
			<view class="uqrcode-h5-save-close" @click="isH5Save = false">
				<view class="uqrcode-h5-save-close-before"></view>
				<view class="uqrcode-h5-save-close-after"></view>
			</view>
		</view>
		<!-- #endif -->

		<!-- 加载效果，可在此替换 -->
		<view class="u-qrcode-makeing" v-if="makeing">
			<image class="u-qrcode-makeing-image" :style="{ width: `${size / 4}px`, height: `${size / 4}px` }"
				src="">
			</image>
		</view>

		<!-- 错误处理，可在此替换 -->
		<view class="u-qrcode-error" v-if="inError"><text class="u-qrcode-error-message">Error, see console.</text>
		</view>

		<!-- <view
      class="u-qrcode-canvas"
      style="background-color: rgba(243, 105, 59, 0.5);position: absolute;top: 0;left: 0;z-index: 999;"
      :style="{
        width: `${size}px`,
        height: `${size}px`
      }"
      v-if="templateOptions.canvasDisplay"
    ></view> -->
	</view>
</template>

<script>
	/* 引入uQRCode核心js */
	import uQRCode from '../../js_sdk/u-qrcode';

	/* 引入nvue所需模块 */
	// #ifdef APP-NVUE
	import {
		enable,
		WeexBridge
	} from './gcanvas';
	let modal = weex.requireModule('modal');
	// #endif

	export default {
		name: 'uqrcode',
		props: {
			/**
			 * canvas组件id
			 */
			canvasId: {
				type: String,
				required: true // canvasId在微信小程序初始值不能为空，created中赋值也不行，必须给一个值，否则挂载组件后无法绘制
			},
			/**
			 * 二维码内容
			 */
			value: {
				type: [String, Number],
				required: true
			},
			/**
			 * 二维码大小
			 */
			size: {
				type: [String, Number],
				default: 354
			},
			/**
			 * 导出的文件类型
			 */
			fileType: {
				type: String,
				default: 'png'
			},
			/**
			 * 选项
			 */
			options: {
				type: Object,
				default: () => {
					return {};
				}
			}
		},
		data() {
			return {
				canvas: undefined,
				canvasContext: undefined,
				makeDelegate: undefined,
				toTempFilePathDelegate: undefined,
				makeing: true,
				inError: false,
				isH5Save: false,
				tempFilePath: '',
				templateOptions: {
					canvasWidth: 0, // canvas宽度
					canvasHeight: 0,
					canvasDisplay: false
				},
				uqrcodeOptions: {
					text: ''
				}
			};
		},
		watch: {
			value: {
				handler() {
					this.remake();
				}
			},
			options: {
				handler() {
					this.remake();
				},
				deep: true
			},
			makeing: {
				handler(val) {
					if (!val) {
						if (typeof this.toTempFilePathDelegate === 'function') {
							this.toTempFilePathDelegate();
						}
					}
				}
			}
		},
		mounted() {
			this.templateOptions.canvasWidth = this.size;
			this.templateOptions.canvasHeight = this.size;
			this.$nextTick(() => {
				this.templateOptions.canvasDisplay = true;
				this.$nextTick(() => {
					this.make();
				});
			});
		},
		methods: {
			/**
			 * 生成二维码
			 */
			make() {
				this.makeing = true;
				this.inError = false;

				if (!this.canvasId || !this.value) {
					if (!this.canvasId) {
						console.error('[uQRCode]: canvasId must be set!');
					}
					if (!this.value) {
						console.error('[uQRCode]: value must be set!');
					}
					this.inError = true;
					this.complete(false);
					clearTimeout(this.makeDelegate);
					return;
				}

				/* 高频重绘节流 */
				clearTimeout(this.makeDelegate);
				this.makeDelegate = setTimeout(() => {
					/* 组件数据 */
					this.templateOptions = this.getTemplateOptions();
					/* uQRCode选项 */
					this.uqrcodeOptions = this.getUqrcodeOptions();

					/* 纠错等级兼容字母写法 */
					if (typeof this.uqrcodeOptions.errorCorrectLevel === 'string') {
						this.uqrcodeOptions.errorCorrectLevel = uQRCode.errorCorrectLevel[this.uqrcodeOptions
							.errorCorrectLevel];
					}
					/* nvue不支持动态修改gcanvas尺寸，除nvue外，默认使用useDynamicSize */
					// #ifndef APP-NVUE
					if (typeof this.options.useDynamicSize === 'undefined') {
						this.uqrcodeOptions.useDynamicSize = true;
					}
					// #endif
					// #ifdef APP-NVUE
					this.uqrcodeOptions.useDynamicSize = false;
					// #endif

					// #ifndef MP-WEIXIN || APP-NVUE
					/* uniapp获取canvas实例方式 */
					const ctx = (this.canvasContext = uni.createCanvasContext(this.canvasId, this));
					/* uniapp获取图像方式 */
					const loadImage = function(src) {
						return new Promise((resolve, reject) => {
							uni.getImageInfo({
								src,
								success: res => {
									resolve(res.path);
								}
							});
						});
					};

					/* 实例化uQRCode：uQRCode选项参数, canvas实例, loadImage方法 */
					let uqrcode = new uQRCode(this.uqrcodeOptions, ctx, loadImage);
					/* 调用uqrcode方法 */
					uqrcode.make();

					/* 使用dynamicSize，可以解决小块间出现白线问题，再通过scale缩放至size，使其达到所设尺寸 */
					this.templateOptions.canvasWidth = uqrcode.options.dynamicSize;
					this.templateOptions.canvasHeight = uqrcode.options.dynamicSize;

					/* 等待canvas重新渲染和节流，并以最后一次重绘为准 */
					this.makeDelegate = setTimeout(() => {
						uqrcode
							.draw()
							.then(() => {
								this.complete(true);
							})
							.catch(() => {
								this.complete(false);
							});
					}, 150);
					// #endif

					// #ifdef MP-WEIXIN
					/* 因为是回调的原因，高频率重绘会出现问题 */
					if (!this.templateOptions.canvasDisplay) {
						return;
					}
					/* 微信小程序获取canvas实例方式 */
					uni
						.createSelectorQuery()
						.in(this) // 在组件内使用需要
						.select(`#${this.canvasId}`)
						.fields({
							node: true,
							size: true
						})
						.exec(res => {
							/* 因为是回调的原因，高频率重绘会出现问题 */
							if (!this.templateOptions.canvasDisplay) {
								return;
							}
							const canvas = (this.canvas = res[0].node);
							const ctx = (this.canvasContext = canvas.getContext('2d'));
							/* 微信小程序获取图像方式 */
							const loadImage = function(src) {
								/* 小程序下获取网络图片信息需先配置download域名白名单才能生效 */
								return new Promise((resolve, reject) => {
									const img = canvas.createImage();
									img.src = src;
									img.onload = () => {
										resolve(img);
									};
								});
							};
							const dpr = uni.getSystemInfoSync().pixelRatio;

							/* 实例化uQRCode：uQRCode选项参数, canvas实例, loadImage方法（微信小程序使用的是canvas2d，不能直接用src，得转成img对象才能使用drawImage） */
							let uqrcode = new uQRCode(this.uqrcodeOptions, ctx, loadImage);
							/* 调用uqrcode方法 */
							uqrcode.make();

							/* 2d的组件设置宽高与实际canvas绘制宽高不是一个,打个比方,组件size=200,canvas.width设置为100,那么绘制出来就是100=200,组件size=400,canvas.width设置为800,绘制大小还是800=400,所以无需理会下方返回的dynamicSize是多少,按dpr重新赋值给canvas即可 */
							this.templateOptions.canvasWidth = uqrcode.options.size;
							this.templateOptions.canvasHeight = uqrcode.options.size;
							/* 使用dynamicSize+scale，可以解决小块间出现白线问题，dpr可以解决模糊问题 */
							canvas.width = uqrcode.options.dynamicSize * dpr;
							canvas.height = uqrcode.options.dynamicSize * dpr;
							ctx.scale(dpr, dpr);

							/* 等待canvas重新渲染和节流，并以最后一次重绘为准 */
							this.makeDelegate = setTimeout(() => {
								uqrcode
									.draw()
									.then(() => {
										this.complete(true);
									})
									.catch(() => {
										this.complete(false);
									});
							}, 150);
						});
					// #endif

					// #ifdef APP-NVUE
					/* 获取元素引用 */
					const gcanvas = this.$refs['gcanvas'];
					/* 通过元素引用获取canvas对象 */
					const canvas = enable(gcanvas, {
						bridge: WeexBridge
					});
					/* 获取绘图所需的上下文，目前不支持3d */
					const ctx = (this.canvasContext = canvas.getContext('2d'));
					/* nvue获取图像方式 */
					const loadImage = function(src) {
						return new Promise((resolve, reject) => {
							/* getImageInfo在nvue的bug：获取同一个路径的图片信息，同一时间第一次获取成功，后续失败，猜测是写入本地时产生文件写入冲突，所以没有返回，特别是对于网络资源 --- js部分已实现队列绘制，已解决此问题 */
							uni.getImageInfo({
								src,
								success: res => {
									resolve(res.path);
								}
							});
						});
					};

					/* 实例化uQRCode：uQRCode选项参数, canvas实例, loadImage方法 */
					let uqrcode = new uQRCode(this.uqrcodeOptions, ctx, loadImage);
					/* 调用uqrcode方法 */
					uqrcode.make();

					/* 等待canvas重新渲染和节流，并以最后一次重绘为准 */
					this.makeDelegate = setTimeout(() => {
						uqrcode
							.draw()
							.then(() => {
								this.complete(true);
							})
							.catch(() => {
								this.complete(false);
							});
					}, 150);
					// #endif
				}, 300);
			},
			/**
			 * 生成完成
			 */
			complete(success = true) {
				setTimeout(() => {
					this.makeing = false;
					this.$emit('complete', {
						success
					});
				}, 150);
			},
			/**
			 * 重新生成
			 */
			remake() {
				this.resetCanvas(() => {
					this.make();
				});
			},
			/**
			 * 重置画布
			 */
			resetCanvas(callback) {
				this.templateOptions.canvasDisplay = false;
				this.$nextTick(() => {
					this.templateOptions.canvasDisplay = true;
					this.$nextTick(() => {
						callback && callback();
					});
				});
			},
			/**
			 * 注册click事件
			 */
			onClick(e) {
				this.$emit('click', e);
			},
			getTemplateOptions() {
				return uQRCode.deepReplace(this.templateOptions, {
					width: this.size,
					height: this.size
				});
			},
			getUqrcodeOptions() {
				return uQRCode.deepReplace(this.options, {
					text: this.value,
					size: this.size
				});
			},
			/**
			 * 导出临时路径
			 */
			toTempFilePath(callback = {}) {
				if (typeof callback.success != 'function') {
					callback.success = () => {};
				}
				if (typeof callback.fail != 'function') {
					callback.fail = () => {};
				}
				if (typeof callback.complete != 'function') {
					callback.complete = () => {};
				}

				if (this.makeing) {
					/* 如果还在生成状态，那当前操作将托管到委托，监听生成完成后再通过委托复调当前方法 */
					this.toTempFilePathDelegate = () => {
						this.toTempFilePath(callback);
					};
					return;
				} else {
					this.toTempFilePathDelegate = null;
				}

				// #ifndef MP-WEIXIN || APP-NVUE
				uni.canvasToTempFilePath({
						canvasId: this.canvasId,
						fileType: this.fileType,
						width: this.templateOptions.canvasWidth,
						height: this.templateOptions.canvasHeight,
						success: res => {
							callback.success(res);
						},
						fail: err => {
							callback.fail(err);
						},
						complete: () => {
							callback.complete();
						}
					},
					this
				);
				// #endif

				// #ifdef MP-WEIXIN
				/* 需要将 data:image/png;base64, 这段去除 writeFile 才能正常打开文件，否则是损坏文件，无法打开*/
				const reg = new RegExp('^data:image/png;base64,', 'g');
				const dataURL = this.canvas.toDataURL().replace(reg, '');
				const fs = wx.getFileSystemManager();
				const tempFilePath = `${wx.env.USER_DATA_PATH}/${new Date().getTime()}${
        Math.random()
          .toString()
          .split('.')[1]
      }.png`;
				fs.writeFile({
					filePath: tempFilePath, // 要写入的文件路径 (本地路径)
					data: dataURL, // base64图片
					encoding: 'base64',
					success: res => {
						callback.success({
							tempFilePath
						});
					},
					fail: err => {
						callback.fail(err);
					},
					complete: () => {
						callback.complete();
					}
				});
				// #endif

				// #ifdef APP-NVUE
				/* 测试过程中，size需要乘以3才能保存完整，3又对应dpr，猜测是与像素比有关，故乘以3。第一次运行无法保存，后续正常，待排查。 */
				const dpr = uni.getSystemInfoSync().pixelRatio;
				this.canvasContext.toTempFilePath(0, 0, this.size * dpr, this.size * dpr, this.size * dpr, this.size * dpr,
					'', 1, res => {
						callback.success(res);
						callback.complete(res);
					});
				// #endif
			},
			/**
			 * 保存
			 */
			save(callback = {}) {
				if (typeof callback.success != 'function') {
					callback.success = () => {};
				}
				if (typeof callback.fail != 'function') {
					callback.fail = () => {};
				}
				if (typeof callback.complete != 'function') {
					callback.complete = () => {};
				}

				this.toTempFilePath({
					success: res => {
						console.log(res);
						// #ifndef H5
						uni.saveImageToPhotosAlbum({
							filePath: res.tempFilePath,
							success: res1 => {
								callback.success(res1);
							},
							fail: err1 => {
								callback.fail(err1);
							},
							complete: () => {
								callback.complete();
							}
						});
						// #endif

						// #ifdef H5
						/* 可以在电脑浏览器下载，移动端iOS不行，安卓微信浏览器不行，安卓外部浏览器可以 */
						this.isH5Save = true;
						this.tempFilePath = res.tempFilePath;
						const aEle = document.createElement('a');
						aEle.download = 'uQRCode'; // 设置下载的文件名，默认是'下载'
						aEle.href = res.tempFilePath;
						document.body.appendChild(aEle);
						aEle.click();
						aEle.remove(); // 下载之后把创建的元素删除
						callback.success({
							errMsg: 'ok'
						});
						callback.complete();
						// #endif
					},
					fail: err => {
						callback.fail(err);
						callback.complete();
					}
				});
			}
		}
	};
</script>

<style>
	.u-qrcode {
		position: relative;
	}

	.u-qrcode-canvas {
		position: absolute;
		top: 0;
		right: 0;
		bottom: 0;
		left: 0;
		transform-origin: top left;
	}

	.u-qrcode-makeing {
		position: absolute;
		top: 0;
		right: 0;
		bottom: 0;
		left: 0;
		/* #ifndef APP-NVUE */
		display: flex;
		/* #endif */
		justify-content: center;
		align-items: center;
	}

	.u-qrcode-makeing-image {
		/* #ifndef APP-NVUE */
		display: block;
		max-width: 120px;
		max-height: 120px;
		/* #endif */
	}

	.u-qrcode-error {
		position: absolute;
		top: 0;
		right: 0;
		bottom: 0;
		left: 0;
		/* #ifndef APP-NVUE */
		display: flex;
		/* #endif */
		justify-content: center;
		align-items: center;
	}

	.u-qrcode-error-message {
		font-size: 12px;
		color: #939291;
	}

	/* #ifdef H5 */
	.uqrcode-h5-save {
		position: fixed;
		top: 0;
		right: 0;
		bottom: 0;
		left: 0;
		z-index: 100;
		background-color: rgba(0, 0, 0, 0.68);
		display: flex;
		flex-direction: column;
		justify-content: center;
		align-items: center;
	}

	.uqrcode-h5-save-image {
		width: 512rpx;
		height: 512rpx;
		padding: 32rpx;
	}

	.uqrcode-h5-save-text {
		margin-top: 20rpx;
		font-size: 32rpx;
		font-weight: 700;
		color: #ffffff;
	}

	.uqrcode-h5-save-close {
		position: relative;
		margin-top: 72rpx;
		width: 40rpx;
		height: 40rpx;
		border: 2rpx solid #ffffff;
		border-radius: 40rpx;
		padding: 10rpx;
	}

	.uqrcode-h5-save-close-before {
		position: absolute;
		top: 50%;
		transform: translateY(-50%) rotate(45deg);
		/* width: 40rpx; */
		width: 20rpx;
		height: 4rpx;
		background: #ffffff;
	}

	.uqrcode-h5-save-close-after {
		position: absolute;
		top: 50%;
		transform: translateY(-50%) rotate(-45deg);
		/* width: 40rpx; */
		width: 20rpx;
		height: 4rpx;
		background: #ffffff;
	}

	/* #endif */
</style>
